from traceback import format_tb
from new import classobj

def create_wrapped_exception(exception, exc_info):
    # TODO: should keep track of all the Wrapped* that we make, and re-use them
    base_klass = exception.__class__
    def wrapped_init(self, exception, exc_info):
        self.__dict__ = exception.__dict__
        _WrappedExceptionMixin.__init__(self, exc_info)
    def wrapped_str(self):
        return str(exception) + "\n" + self.original_backtrace
    wrapped_klass = classobj("Wrapped%s" % base_klass.__name__, (base_klass, _WrappedExceptionMixin), {"__init__": wrapped_init, "__str__": wrapped_str})
    return wrapped_klass(exception, exc_info)


class _WrappedExceptionMixin(object):
    """the methods in this class are used to mix in with any
    Wrapped* exception.  In other words, every Wrapped* exception
    will have this interface"""
    
    def _get_backtrace(self):
        if self.__exc_info[2]:
            # TODO: use indenting
            return "\n".join( format_tb(self.__exc_info[2]) )
        else:
            return "<traceback omitted>"
    
    original_exception = property(lambda self: self.__exc_info[0])
    original_exc_info = property(lambda self: self.__exc_info)
    original_backtrace = property(_get_backtrace)
    
    def __init__(self, exc_info):
        # FIXME: python documentation warns of circular reference:
        #
        #     Warning: Assigning the traceback return value to a local variable in
        #     a function that is handling an exception will cause a circular reference.
        #     This will prevent anything referenced by a local variable in the same
        #     function or by the traceback from being garbage collected. Since most functions
        #     don't need access to the traceback, the best solution is to use something like
        #     exctype, value = sys.exc_info()[:2] to extract only the exception type and value.
        #     If you do need the traceback, make sure to delete it after use (best done with a
        #     try ... finally statement) or to call exc_info() in a function that does not itself
        #     handle an exception. Note: Beginning with Python 2.2, such cycles are automatically
        #     reclaimed when garbage collection is enabled and they become unreachable, but it
        #     remains more efficient to avoid creating cycles.
        self.__exc_info = exc_info